Has-a as Is-a

Shlomi Fish on 2008-03-17T21:14:45

Recently I've encountered a modularity issue in my code, I had a function like the following

sub _is_event_pass
{
        return ($self->_event->is_ok() ||
                $self->_event->is_skip() ||
                $self->_event->is_todo()
               );
}

As you can see all I'm doing is calling methods on the _event. The right thing to do would have been to move it as method to the class of the _event() that will then use the object's instance itself. Now the problem is that the _event() field can be any of the TAP::Result:: hierarchy of classes

And it wouldn't be a good idea to sub-class and re-bless all of them.

So what to do?

What I eventually did is create an EventWrapper class, that has a field which is the actual object. Then I'm delegating all the methods of the TAP::Result classes that I use to that field. I.e:

sub is_ok
{
        my $self = shift;

        return $self->_tp_result()->is_ok();
}

sub is_todo
{
        my $self = shift;

        return $self->_tp_result()->is_todo();
}

(only I'm auto-generating these methods of-course).

And then I defined the is_pass function there like this:

sub is_pass
{
        my $self = shift;

        return ($self->is_ok() || $self->is_todo() || $self->is_skip());
}

Which works because these methods are delegated.

So ::EventWrapper behaves like TAP::Result ("is-a") while actually only containing it ("has-a"). It's a useful technique.

Of course, I made a good use of the fact that Perl is dynamically-typed and evaluates methods at run-time. If I wanted to do the same in strongly-typed OO languages, then I would have needed to figure out a way to delegate to all the methods of the various different classes in the hiearachy. Perhaps using run-time classes.